﻿Imports System.IO
Imports System.Reflection
Imports System.Text

Imports System.Reflection.Emit

Imports System.CodeDom
Imports System.CodeDom.Compiler

Imports Microsoft.CSharp
Imports Microsoft.VisualBasic

Imports CodeDefinition

Public Class CodeGenerator_DO

    Private Const cBASE_CLASS_NAME As String = "base_DO"
    Private Const cBASE_NAMESPACE_NAME As String = "generator_base_do"

    Private Const cMEMBER_PREFIX As String = "m_"

    Public Shared Function DoCreation(ByVal aXMLConfigurationFile As String, ByVal aGeneratorFeedback As GeneratorFeedback) As GeneratorFeedback

        'if not set create a new one
        If aGeneratorFeedback Is Nothing Then aGeneratorFeedback = New GeneratorFeedback

        'check if the file exit
        If Not System.IO.File.Exists(aXMLConfigurationFile) Then Throw New ArgumentException(String.Format("The configuration file '{0}' could not be found", aXMLConfigurationFile))

        'create the appliationobject
        Dim myApplicationObject As ApplicationObject = CodeDefinition.ApplicationObject.CreateApplicationObjectFromXML(aXMLConfigurationFile)

        'create the output directory
        Dim myDirectory As String = myApplicationObject.ApplicationDirectory
        'create directory if not exit
        If Not Directory.Exists(myDirectory) Then Directory.CreateDirectory(myDirectory)

        ' create the base class
        CreateBaseClass(anApplicationObject:=myApplicationObject, aGeneratorFeedback:=aGeneratorFeedback)

        ' create classes
        For Each myClassobject As ClassObject In myApplicationObject.ClassObjects
            CreateClass(anApplicationObject:=myApplicationObject, _
                        aClassObject:=myClassobject, _
                        aGeneratorFeedback:=aGeneratorFeedback)
        Next

        Return aGeneratorFeedback

    End Function

    Private Shared Sub CreateBaseClass(ByVal anApplicationObject As ApplicationObject, ByVal aGeneratorFeedback As GeneratorFeedback)
        Dim myClassName As String = cBASE_CLASS_NAME

        'create the Feedback provider
        Dim myDOClassDetailInfo As DOClassDetailInfo = aGeneratorFeedback.DOClassInfo.AddClassName(myClassName)

        Dim myTargetUnit = New CodeCompileUnit()

        'create namespace
        Dim myCodeNamespace As New CodeNamespace(cBASE_NAMESPACE_NAME)

        ' create a new class named "base_do"
        Dim myClassObject As New CodeTypeDeclaration(String.Format("[{0}]", myClassName))
        With myClassObject
            .Attributes = MemberAttributes.Public
            .IsClass = True
            .IsEnum = False
            .IsInterface = False
            .IsStruct = False
            .TypeAttributes = TypeAttributes.Class
        End With

        'add class to namespace
        myCodeNamespace.Types.Add(myClassObject)

        'add the namespace into the Unit
        myTargetUnit.Namespaces.Add(myCodeNamespace)

        ' create uID Property
        Dim myCodeExpression As CodeExpression = New CodeMethodInvokeExpression(New CodeTypeReferenceExpression(GetType(Guid)), "NewGuid")
        myClassObject.Members.AddRange(CreateProperty(aPropertyName:="UniqueObjectIdentifier", _
                                                          aPropertyType:=GetType(Guid), _
                                                          aHasGet:=True, _
                                                          aHasSet:=True, _
                                                          anAttributes:=MemberAttributes.Public, _
                                                          aDefaultValue:=myCodeExpression, _
                                                          aDOClassDetailInfo:=myDOClassDetailInfo))


        WriteClass(anApplicationObject:=anApplicationObject, _
                   aDOClassDetailInfo:=myDOClassDetailInfo, _
                   aClassName:=myClassName, _
                   aTargetUnit:=myTargetUnit)

    End Sub

    Private Shared Sub CreateClass(ByVal anApplicationObject As ApplicationObject, ByVal aClassObject As ClassObject, ByVal aGeneratorFeedback As GeneratorFeedback)
        Dim myClassName As String = aClassObject.Name

        'create the Feedback provider
        Dim myDOClassDetailInfo As DOClassDetailInfo = aGeneratorFeedback.DOClassInfo.AddClassName(myClassName)

        Dim myTargetUnit = New CodeCompileUnit()

        'create namespace
        Dim myCodeNamespace As New CodeNamespace(aClassObject.ClassNamespaceName)

        'Import the namespaces
        For Each myNamespace As String In aClassObject.NamespacesToImport
            'imports namespaces to uses in the class
            myCodeNamespace.Imports.Add(New CodeNamespaceImport(myNamespace))
        Next

        aClassObject.MemberAttributeObject = New MemberAttributeObject() With {.Public = True}

        ' create a new class named "SimpleClass"
        Dim myClassObject As New CodeTypeDeclaration(String.Format("[{0}]", myClassName))
        With myClassObject
            .Attributes = aClassObject.MemberAttributeObject.GetMemberAttributes
            .IsClass = True
            .IsEnum = False
            .IsInterface = False
            .IsStruct = False
            .TypeAttributes = TypeAttributes.Class
        End With

        ' add base class reference
        myClassObject.BaseTypes.Add(New CodeTypeReference(String.Format("{0}.{1}", cBASE_NAMESPACE_NAME, cBASE_CLASS_NAME)))

        ' add class to namespace
        myCodeNamespace.Types.Add(myClassObject)

        ' add the namespace into the Unit
        myTargetUnit.Namespaces.Add(myCodeNamespace)

        'loop over the properties
        For Each myPropertyObject As PropertyObject In aClassObject.PropertyObjects
            Dim myName As String = myPropertyObject.Name
            Dim myAttributes As MemberAttributes = myPropertyObject.MemberAttributeObject.GetMemberAttributes
            'get the type from the definition
            Dim myPropertyType As Type = myPropertyObject.TypeAttributes

            myClassObject.Members.AddRange(CreateProperty(aPropertyName:=myName, _
                                                          aPropertyType:=myPropertyType, _
                                                          aHasGet:=myPropertyObject.HasGet, _
                                                          aHasSet:=myPropertyObject.HasSet, _
                                                          anAttributes:=myAttributes, _
                                                          aDefaultValue:=Nothing, _
                                                          aDOClassDetailInfo:=myDOClassDetailInfo))
        Next

        ' add the method to the class
        myClassObject.Members.Add(CreateMethodUpdateMe(aClassobject:=aClassObject, _
                                                       aDOClassDetailInfo:=myDOClassDetailInfo))

        WriteClass(anApplicationObject:=anApplicationObject, _
                   aDOClassDetailInfo:=myDOClassDetailInfo, _
                   aClassName:=myClassName, _
                   aTargetUnit:=myTargetUnit)
    End Sub

    Private Shared Sub WriteClass(ByVal anApplicationObject As ApplicationObject, _
                                  ByVal aDOClassDetailInfo As DOClassDetailInfo, _
                                  ByVal aClassName As String, _
                                  ByVal aTargetUnit As CodeCompileUnit)

        ' define the generatortype
        Dim myProviderType As ApplicationObject.enmGeneratorType = anApplicationObject.GeneratorType

        'define the options
        Dim myOptions As New CodeGeneratorOptions()
        With myOptions
            '.IndentString = anApplicationObject.IdentString
            .BlankLinesBetweenMembers = anApplicationObject.BlankLinesBetweenMembers
            .VerbatimOrder = anApplicationObject.VerbatimOrder
        End With

        ' define the base path for the export
        Dim myPathNameVB As String = anApplicationObject.GetExportDirectory()
        Dim myPathNameCSharp As String = anApplicationObject.GetExportDirectory()

        ' define the provider to use
        Dim myProviderVB As CodeDomProvider = Nothing
        Dim myProviderCSharp As CodeDomProvider = Nothing
        Select Case myProviderType
            Case ApplicationObject.enmGeneratorType.VisualBasic
                myProviderVB = CodeDomProvider.CreateProvider(ApplicationObject.enmGeneratorType.VisualBasic.ToString)
                myPathNameVB = Tools.AppendPath(myPathNameVB, "VisualBasic\DO")
                Directory.CreateDirectory(myPathNameVB)

            Case ApplicationObject.enmGeneratorType.CSharp
                myProviderCSharp = CodeDomProvider.CreateProvider(ApplicationObject.enmGeneratorType.CSharp.ToString)
                myPathNameCSharp = Tools.AppendPath(myPathNameCSharp, "CSharp\DO")
                Directory.CreateDirectory(myPathNameCSharp)

            Case ApplicationObject.enmGeneratorType.VisualBasicAndCSharp
                ' VB
                myProviderVB = CodeDomProvider.CreateProvider(ApplicationObject.enmGeneratorType.VisualBasic.ToString)
                myPathNameVB = Tools.AppendPath(myPathNameVB, "VisualBasic\DO")
                Directory.CreateDirectory(myPathNameVB)
                ' C#
                myProviderCSharp = CodeDomProvider.CreateProvider(ApplicationObject.enmGeneratorType.CSharp.ToString)
                myPathNameCSharp = Tools.AppendPath(myPathNameCSharp, "CSharp\DO")
                Directory.CreateDirectory(myPathNameCSharp)

        End Select

        Dim myClassFileNameVB As String = Tools.CreateFilePath(aPath:=myPathNameVB, aFilename:=aClassName & ".vb")
        Dim myClassFileNameCSharp As String = Tools.CreateFilePath(aPath:=myPathNameCSharp, aFilename:=aClassName & ".cs")

        Dim myStreamVB As Stream = File.Open(myClassFileNameVB, FileMode.Create)
        Dim myStreamWriterVB As New StreamWriter(myStreamVB)

        Dim myStreamCSharp As Stream = File.Open(myClassFileNameCSharp, FileMode.Create)
        Dim myStreamWriterCSharp As New StreamWriter(myStreamCSharp)
        Try
            If Not myProviderVB Is Nothing Then myProviderVB.GenerateCodeFromCompileUnit(aTargetUnit, myStreamWriterVB, myOptions)
            If Not myProviderCSharp Is Nothing Then myProviderCSharp.GenerateCodeFromCompileUnit(aTargetUnit, myStreamWriterCSharp, myOptions)
        Finally
            myStreamWriterVB.Dispose()
            myStreamWriterCSharp.Dispose()
            myStreamVB.Dispose()
            myStreamCSharp.Dispose()
        End Try


    End Sub

    'create the Method UpdateMe
    Private Shared Function CreateMethodUpdateMe(ByVal aClassobject As ClassObject, ByVal aDOClassDetailInfo As DOClassDetailInfo) As CodeMemberMethod
        'Create the UpdateSelf Sub to update the properties with the values from another object (like a merge)
        Dim myMethodName As String = "UpdateMe"
        Dim myMethodUpdateSelf As New CodeMemberMethod With {.Name = myMethodName, .Attributes = MemberAttributes.Public}

        'add the Method into the feedback provider
        aDOClassDetailInfo.AddMethodName(myMethodName)

        ' ClassName
        Dim myClassName As String = aClassobject.Name

        ' create the codeType of the method
        Dim myMethodParameterCodeType As New CodeTypeReference With {.BaseType = String.Format("[{0}]", myClassName)}

        ' create a Function / if you have a sub, then remark this
        'myMethodUpdateSelf.ReturnType = myMethodParameterCodeType

        ' get the parameter name
        Dim myParameterName = CreateParameterName(myClassName)

        ' create the parameter
        Dim myParameter As CodeParameterDeclarationExpression = CreateMethodParameterName(aParameterName:=myParameterName, _
                                                                                          aCodeType:=myMethodParameterCodeType)
        ' adding the parameter
        myMethodUpdateSelf.Parameters.Add(myParameter)

        ' adding the properties
        For Each myPropertyObject As PropertyObject In aClassobject.PropertyObjects
            ' Assigns the objectvalue into the current value.
            Dim myPropertyName As String = myPropertyObject.Name
            Dim myLeftExpression As New CodePropertyReferenceExpression(New CodeThisReferenceExpression(), myPropertyName)
            Dim myRightExpression As New CodePropertyReferenceExpression(New CodeVariableReferenceExpression(myParameterName), myPropertyName)
            Dim myLine As New CodeAssignStatement(myLeftExpression, myRightExpression)
            myMethodUpdateSelf.Statements.Add(myLine)
        Next

        ' define the return value
        'Dim myReturnValue As New CodeMethodReturnStatement(New CodeThisReferenceExpression)
        'myMethodUpdateSelf.Statements.Add(myReturnValue)

        Return myMethodUpdateSelf
    End Function

    Private Shared Function DeclareVariable(ByVal aDataType As Type, ByVal aName As String) As CodeVariableDeclarationStatement

        ' Get a CodeTypeReference for the Type 
        ' of the variable we are about to
        ' create.   This will allow us not to 
        ' have to get bogged down in the
        ' language specific details of specifying 
        ' data types.
        Dim myTR As New CodeTypeReference(aDataType)

        ' The CodeVariableDeclarationStatement 
        ' will allow us to not have to
        ' worry about such details as whether 
        ' the Data Type or the variable name
        ' comes first or whether or not a key 
        ' word such  as Dim is required.
        Dim myDeclaration As New CodeVariableDeclarationStatement(myTR, aName)

        ' The CodeObjectCreateExpression handles 
        ' all of the details for calling
        ' constructors.   In most cases, this 
        ' will be new, but sometimes it is New.
        ' At any rate, we don't want to  have to 
        ' worry about such details.
        Dim newStatement As New CodeObjectCreateExpression()

        ' Here we specify the object whose 
        ' constructor we want to invoke.
        newStatement.CreateType = myTR

        'Here we specify that variable will be 
        'initialized by calling its constructor.
        myDeclaration.InitExpression = newStatement
        Return myDeclaration
    End Function

    'create a property
    Private Shared Function CreateProperty(ByVal aPropertyName As String, _
                                           ByVal aPropertyType As Type, _
                                           ByVal aHasGet As Boolean, _
                                           ByVal aHasSet As Boolean, _
                                           ByVal anAttributes As MemberAttributes, _
                                           ByVal aDefaultValue As CodeExpression, _
                                           ByVal aDOClassDetailInfo As DOClassDetailInfo) As CodeTypeMemberCollection
        Dim myCodeTypeMemberCollection As New CodeTypeMemberCollection

        'add the Property into the feedback provider
        aDOClassDetailInfo.AddPropertyName(aPropertyName)

        'member property
        Dim myPrivateField As New CodeMemberField(aPropertyType, String.Format("[{0}]", cMEMBER_PREFIX & aPropertyName)) _
                                    With {.Attributes = MemberAttributes.Private}
        If Not aDefaultValue Is Nothing Then
            myPrivateField.InitExpression = aDefaultValue
        End If

        ' add it to the class Members collection.
        myCodeTypeMemberCollection.Add(myPrivateField)

        'property
        Dim myPropertyField As New CodeMemberProperty() With {.Name = aPropertyName, _
                                                .Attributes = anAttributes, _
                                                .HasGet = aHasGet, _
                                                .HasSet = aHasSet, _
                                                .Type = New CodeTypeReference(aPropertyType)}
        'add the property code
        myPropertyField.SetStatements.Add(New CodeSnippetExpression(String.Format("{0}{1} = value", cMEMBER_PREFIX, aPropertyName)))
        myPropertyField.GetStatements.Add(New CodeSnippetExpression(String.Format("return {0}{1}", cMEMBER_PREFIX, aPropertyName)))

        'myPropertyField.
        ' add it to the class Members collection.
        myCodeTypeMemberCollection.Add(myPropertyField)

        Return myCodeTypeMemberCollection

    End Function

    ' Create a proper method Parameter Name
    Private Shared Function CreateMethodParameterName(ByVal aParameterName As String, ByVal aCodeType As CodeTypeReference) As CodeParameterDeclarationExpression
        Dim myMethodParameter As New CodeParameterDeclarationExpression With {.Name = aParameterName, .Type = aCodeType}
        Return myMethodParameter
    End Function

    'create a Variable Name
    Private Shared Function CreateVariableName(ByVal aName As String) As String
        Return "my" & aName
    End Function

    'create a Parametername
    Private Shared Function CreateParameterName(ByVal aFieldName As String) As String
        Dim myParameterName As New StringBuilder
        myParameterName.Append("a"c)
        If aFieldName.ToLower.StartsWith("a") OrElse _
           aFieldName.ToLower.StartsWith("e") OrElse _
           aFieldName.ToLower.StartsWith("i") OrElse _
           aFieldName.ToLower.StartsWith("o") OrElse _
           aFieldName.ToLower.StartsWith("u") Then
            myParameterName.Append("n"c)
        End If
        myParameterName.Append(aFieldName)
        Return myParameterName.ToString
    End Function

    'create a field
    Private Shared Function CreateField(ByVal aFieldType As Type, _
                                 ByVal aFieldName As String, _
                                 ByVal aScope As MemberAttributes) As CodeMemberField
        Dim myCMF As New CodeMemberField(Type:=aFieldType, name:=aFieldName)
        myCMF.Attributes = aScope
        Return myCMF
    End Function

    'create a constructor
    Private Shared Function CreateConstructor(ByVal aClass As CodeTypeDeclaration, _
                                       ByVal aScope As MemberAttributes, _
                                       ByVal aParams As CodeParameterDeclarationExpressionCollection, _
                                       ByVal aStatements As CodeStatementCollection) As CodeConstructor

        'create a class constructor wieht the appropriate scope attribute
        Dim myClassConstructor As New CodeConstructor() With {.Attributes = aScope}
        'assign parameter
        If Not aParams Is Nothing Then myClassConstructor.Parameters.AddRange(aParams)
        'add statements
        If Not aStatements Is Nothing Then myClassConstructor.Statements.AddRange(aStatements)
        Return myClassConstructor
    End Function

    'assign a value to a member field
    Private Shared Function CreateFieldAssignment(ByVal aClass As CodeTypeDeclaration, _
                                           ByVal anAssignTo As String, _
                                           ByVal anAssignedFrom As String) As CodeAssignStatement
        Return New CodeAssignStatement(New CodeFieldReferenceExpression( _
                                           New CodeThisReferenceExpression(), anAssignTo), _
                                           New CodeArgumentReferenceExpression(anAssignedFrom))
    End Function

End Class

